﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pdm.Commons;
using System.Collections;
using System.Diagnostics;

namespace Pdm.Solver
{
    public class Bisection
    {
        private Queue<BisectionProblem> problems = new Queue<BisectionProblem>();
        private List<PdmOptimization> transitions;
        private Solver solver;
        private double uBound = -1;
        private double precision = 0.1E-10;

        public Bisection(DmuList dmuList, Dmu objectiveDmu, List<SpecificationInstance> urmWeights, SolverModell solver, double? precision)
        {
            this.transitions = new List<PdmOptimization>();
            if (precision.HasValue)
                this.precision = precision.Value;
            switch (solver)
            {
                case SolverModell.Heuristik:
                    this.solver = new HeuristikSolver(dmuList, objectiveDmu, urmWeights);
                    break;
                case SolverModell.Global:
                    //this.solver = new GlobalSolver(dmuList, objectiveDmu, urmWeights);
                    break;
            }
        }
        public List<PdmOptimization> getTransitions()
        {
            return this.getTransitions(this.getAlphaUBound());
        }
        public List<PdmOptimization> getTransitions(double alphaUpperBound)
        {
            PdmOptimization lowBo = solver.getOptimization(0);
            PdmOptimization uppBo = solver.getOptimization(alphaUpperBound);
            //Debug.WriteLine("UpperBound: " + alphaUpperBound  + " ProfileDistance: " +uppBo.getProfileDistance());
            transitions.Add(lowBo);
            //transitions.add(uppBo);
            problems.Enqueue(new BisectionProblem(lowBo, uppBo));
            while (problems.Count != 0)
            {
                solveProblem(problems.Dequeue());
            }
            //Todo
            //System.Collections.sort(transitions);
            //Debug.WriteLine("size " + transitions.size());
            return transitions;
        }
        private void solveProblem(BisectionProblem prob)
        {
            //Debug.WriteLine("Problem: " + prob.lowBound.getAlpha() + " ||| " + prob.uppBound.getAlpha());
            try
            {
                if (prob.uppBound.alpha - prob.lowBound.alpha > this.precision)
                {
                    if (prob.lowBound.profileDistance != prob.uppBound.profileDistance)
                    {
                        //Debug.WriteLine("LowBo " + prob.lowBound.getProfileDistance() + " - UppBo " + prob.uppBound.getProfileDistance());
                        PdmOptimization midOpt = solver.getOptimization((prob.lowBound.alpha + prob.uppBound.alpha) / 2);
                        if (Math.Abs(prob.lowBound.profileDistance - midOpt.profileDistance) >= precision)
                        {
                            //Debug.WriteLine("New Lower Problem: " + prob.lowBound.getAlpha() + " - " + midOpt.getAlpha());
                            //Debug.WriteLine("          Problem: " + prob.lowBound.getProfileDistance() + " - " + midOpt.getProfileDistance());
                            problems.Enqueue(new BisectionProblem(prob.lowBound, midOpt));
                        }
                        if (Math.Abs(midOpt.profileDistance - prob.uppBound.profileDistance) >= precision)
                        {
                            //Debug.WriteLine("New Upper Problem: " + midOpt.getAlpha() +" - " + prob.uppBound.getAlpha());
                            //Debug.WriteLine("          Problem: " + prob.lowBound.getProfileDistance() + " - " + midOpt.getProfileDistance());
                            problems.Enqueue(new BisectionProblem(midOpt, prob.uppBound));
                        }
                    }
                }
                else
                {
                    //Debug.WriteLine("Precision reached");
                    transitions.Add(prob.uppBound);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        private double getAlphaUBound()
        {
            if (this.uBound == -1)
            {
                double alpha = 0.5;
                PdmOptimization curr = solver.getOptimization(alpha);
                while (curr.profileDistance > 0.1E-10)
                {
                    alpha *= 2;
                    curr = solver.getOptimization(alpha);
                }
                this.uBound = curr.alpha;
            }
            return this.uBound;
        }
        class BisectionProblem
        {
            public PdmOptimization lowBound;
            public PdmOptimization uppBound;

            public BisectionProblem(PdmOptimization lowBound, PdmOptimization uppBound)
            {
                this.lowBound = lowBound;
                this.uppBound = uppBound;
            }
        }
        public enum SolverModell
        {
            Heuristik, Global
        }
    }
}
